package org.jiajie.coffeecodes.concurrent.readwrite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;

/**
 * 使用ReadWrite快速实现一个缓存的通用工具类
 *
 * @author jay
 */
public class ReadWriteLockCache<K, V> {
    private final Map<K, V> m = new HashMap<>();
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();

    /**
     * 读锁
     */
    private final Lock r = rwl.readLock();

    /**
     * 写锁
     */
    private final Lock w = rwl.writeLock();

    /**
     * 读缓存
     *
     * @param key key
     * @return value
     */
    public V get(K key) {
        V v = null;
        // 读缓存
        r.lock();
        try {
            v = m.get(key);
        } finally {
            r.unlock();
        }
        // 缓存中存在，返回
        if (v != null) {
            return v;
        }
        // 缓存中不存在，查询数据库
        w.lock();
        try {
            // 再次验证缓存中是否存在数据
            v = m.get(key);
            if (v == null) {
                // 查询数据库
                //v = "从数据库中查询出来的数据";
                m.put(key, v);
            }
        } finally {
            w.unlock();
        }
        return v;
    }

    /**
     * 读缓存
     *
     * @param key key
     * @return value
     */
    public V get(K key, Supplier<V> optionsWhenMiss) {
        V value;
        // 读缓存
        r.lock();
        try {
            value = m.get(key);
        } finally {
            r.unlock();
        }
        // 缓存中存在，返回
        if (value != null) {
            return value;
        }
        // 缓存中不存在，查询数据库
        w.lock();
        try {
            // 再次验证缓存中是否存在数据
            value = m.get(key);
            if (value == null) {
                // 查询数据库
                value = optionsWhenMiss.get();
                m.put(key, value);
            }
        } finally {
            w.unlock();
        }
        return value;
    }

    /**
     * 写缓存
     *
     * @param key   key
     * @param value value
     * @return value
     */
    public V put(K key, V value) {
        w.lock();
        try {
            return m.put(key, value);
        } finally {
            w.unlock();
        }
    }
}
